package applet;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import applet.forces.GlobalForce;
import parts.mass.Mass;
import parts.spring.Spring;


/**
 * Simulates objects moving around in a bounded environment.
 * Calls the update methods for both masses and springs
 * (who each know how to update themselves). Also will create
 * and update the mass and spring created by mouse dragging if
 * activated. Simulation also holds all of the Global Forces info for
 * the simulation. This list of info is passed to each Mass's update method,
 * who then uses it to update itself based on which global forces are
 * activated.
 *
 * @author Robert C. Duvall
 */
public class Simulation {
    private List<Mass> myMasses;
    private List<Spring> mySprings;
    private Canvas myContainer;
    private GlobalForce myGlobalForces;
    private Mass myMouseMass;
    private Spring myMouseSpring;
    private int myTop;
    private int myBottom;
    private int myLeft;
    private int myRight;

    /**
     * Create a Canvas with the given size.
     *
     * @param container of the simulation
     */
    public Simulation (Canvas container) {
        myMasses = new ArrayList<Mass>();
        mySprings = new ArrayList<Spring>();
        myGlobalForces = new GlobalForce();
        myMouseMass = null;
        myMouseSpring = null;
        myContainer = container;
        myTop = 0;

        // java would not give me the size of container here
        // (says it is 0)
        myBottom = 800;
        myLeft = 0;
        myRight = 800;
    }

    /**
     * Adds a mass to the simulation.
     *
     * @param mass to be added
     */
    public void add (Mass mass) {
        myMasses.add(mass);
    }

    /**
     * Adds a spring to the simulation.
     *
     * @param spring to be added
     */
    public void add (Spring spring) {
        mySprings.add(spring);
    }

    /**
     * Returns the info for the
     * global forces currently in
     * effect.
     */
    public GlobalForce getGlobal () {
        return myGlobalForces;
    }

    /**
     * Creates a new spring to connect to the closest mass
     * in any assembly. The k value is defaulted to .05. Also,
     * the mass created by dragging the mouse moves along with
     * the pointer as long as it stays clicked.
     *
     * @param mousePosition the current mouse position
     */
    public void dragMass(Point2D mousePosition) {
        if (myMouseMass == null && myMouseSpring == null) {
            Mass closestMass = getClosestMass(mousePosition);
            double mouseDistance = closestMass.getDistFromMass(mousePosition);

            myMouseMass = new Mass(-1, mousePosition.getX(),
                    mousePosition.getY(), 10);
            myMouseSpring = new Spring(closestMass, myMouseMass,
                    mouseDistance, .5);
        }

        if (myMouseMass != null) {
            myMouseMass.setCenter(mousePosition.getX(), mousePosition.getY());
        }
    }

    /**
     * Removes the mass and spring created
     * by the mouse dragged event.
     */
    public void clearMouseDrag() {
        myMouseMass = null;
        myMouseSpring = null;
    }

    /**
     * Paint all shapes on the canvas. If the mouse
     * dragged event is happening, also paint the
     * new mass and spring.
     *
     * @param pen used to paint shape on the screen
     */
    public void paint (Graphics2D pen) {
        for (Spring s : mySprings) {
            s.paint(pen);
        }
        for (Mass m : myMasses) {
            m.paint(pen);
        }

        if (myMouseMass != null && myMouseSpring != null) {
            myMouseMass.paint(pen);
            myMouseSpring.paint(pen);
        }
    }

    /**
     * Called by each step of timer, multiple times per second.
     *
     * This should update the state of the animated shapes by just
     * a little so they appear to move over time. Also, if the mouse
     * dragged event is happening, update the new mass and spring.
     *
     * @param dt is the time change
     */
    public void update (double dt) {
        updateCenter();
        updateMovers(dt);
    }

    void updateMovers (double dt) {
        if (myMouseMass != null && myMouseSpring != null) {
            myMouseSpring.update(this, dt);
            myMouseMass.update(this, dt, myGlobalForces);
        }

        for (Spring s : mySprings) {
            s.update(this, dt);
        }
        for (Mass m : myMasses) {
            m.update(this, dt, myGlobalForces);
        }
    }

    void updateCenter () {
        double massSum = 0;
        double posMassSumX = 0;
        double posMassSumY = 0;

        for (Mass m : myMasses) {
            massSum = massSum + m.getMass();
            posMassSumX = posMassSumX + m.getMass() * m.getCenter().getX();
            posMassSumY = posMassSumY + m.getMass() * m.getCenter().getY();
        }

        int xbar = (int) (posMassSumX / massSum);
        int ybar = (int) (posMassSumY / massSum);

        myGlobalForces.setCOMPoint(xbar, ybar);
    }

    /**
     * Clears all masses and springs
     * on the screen.
     */
    public void clearAll () {
        myMasses.clear();
        mySprings.clear();
    }

    /**
     * Returns size (in pixels) of the game area.
     */
    public Dimension getSize () {
        return myContainer.getSize();
    }

    /**
     * Finds a mass in the simulation.
     *
     * @param id of the mass to be returned
     */
    public Mass getMass (double id) {
        for (Mass m : myMasses) {
            if (m.match(id)) {
                return m;
            }
        }
        return null;
    }

    /**
     * Increases the size of the operating area
     * on all sides by 10 pixels.
     */
    public void addSize () {
        myTop -= 10;
        myBottom += 10;
        myLeft -= 10;
        myRight += 10;
    }

    /**
     * Decreases the size of the operating
     * area on all sides by 10 pixels.
     */
    public void subSize () {
        myTop += 10;
        myBottom -= 10;
        myLeft += 10;
        myRight -= 10;
    }

    /**
     * Returns the location of the
     * top of the simulation.
     */
    public int getTop () {
        return myTop;
    }

    /**
     * Returns the location of the
     * bottom of the simulation.
     */
    public int getBottom () {
        return myBottom;
    }

    /**
     * Returns the location of the
     * left boundary of the simulation.
     */
    public int getLeft () {
        return myLeft;
    }

    /**
     * Returns the location of the
     * right boundary of the simulation.
     */
    public int getRight () {
        return myRight;
    }

    /**
     * Gets the closest mass to the current
     * mouse position.
     *
     * @param point of the current mouse position
     * @return
     */
    public Mass getClosestMass(Point2D point) {
        double minDist = Integer.MAX_VALUE;
        Mass closestMass = myMasses.get(0);
        for (Mass m : myMasses) {
            double tempDist = m.getDistFromMass(point);
            if (tempDist < minDist) {
                minDist = tempDist;
                closestMass = m;
            }
        }
        return closestMass;
    }
}
